2 * wiggle - apply rejected patches
4 * Copyright (C) 2003 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program.
22 * Email: <neilb@suse.de>
26 * Wiggle is a tool for working with patches that don't quite apply properly.
27 * It provides functionality similar to 'diff' and 'merge' but can
28 * work at the level of individual words thus allowing the merging of
29 * two changes that affect the same line, but not the same parts of that line.
31 * Wiggle can also read patch and merge files. Unlike 'merge' it does not
32 * need to be given three separate files, but can be given a file and a patch
33 * and it will extract the pieces of the two other files that it needs from
36 * Wiggle performs one of three core function:
37 * --extract -x extract part of a patch or merge file
38 * --diff -d report differences between two files
39 * --merge -m merge the changes between two files into a third file
41 * This is also a --browse (-B) mode which provides interactive access
44 * To perform these, wiggle requires 1, 2, or 3 input streams respectively.
45 * I can get these from individual files, from a diff (unified or context) or
49 * If one file is given, it is a merge file (output of 'merge').
50 * If two files are given, the second is assumed to be a patch,
51 * the first is a normal file.
52 * If three files are given, they are taken to be normal files.
55 * If one file is given, it is a patch
56 * If two files are given, they are normal files.
59 * Only one file can be given. -p indicates it is a patch,
60 * otherwise it is a merge.
61 * One of the flags -1 -2 or -3 must also be given and they indicate which
62 * part of the patch or merge to extract.
64 * Difference calculation and merging is performed on lines (-l) or words (-w).
65 * Each 'word' is either 1/all alphnumeric (or '_'), 2/ all space or tab,
66 * or 3/ any other single character.
68 * In the case of -w, an initial diff is computed based on non-trivial words
69 * which includes alhpanumeric words and newlines.
71 * This diff is computed from the ends of the file and is used to find
72 * a suitable starting point and range. Then a more precise diff is
73 * computed over that restricted range
75 * Other options available are:
76 * --replace -r replace first file with result of merge.
77 * --help -h provide help
78 * --version -v version
80 * Defaults are --merge --words
96 void die(char *reason)
98 fprintf(stderr, "%s: fatal error: %s failure\n", Cmd, reason);
102 void check_dir(char *name, int fd)
105 if (fstat(fd, &st) != 0) {
106 fprintf(stderr, "%s: fatal: %s is strange\n", Cmd, name);
109 if (S_ISDIR(st.st_mode)) {
110 fprintf(stderr, "%s: %s is a directory\n", Cmd, name);
115 void *xmalloc(int size)
117 void *rv = malloc(size);
119 char *msg = "Failed to allocate memory - aborting\n";
120 write(2, msg, strlen(msg));
126 void printword(FILE *f, struct elmnt e)
129 fprintf(f, "%.*s", e.plen + e.prefix,
133 sscanf(e.start+1, "%d %d %d", &a, &b, &c);
134 fprintf(f, "*** %d,%d **** %d%s", b, c, a, e.start+18);
138 static void printsep(struct elmnt e1, struct elmnt e2)
140 int a, b, c, d, e, f;
141 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
142 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
143 printf("@@ -%d,%d +%d,%d @@%s", b, c, e, f, e1.start+18);
146 static int extract(int argc, char *argv[], int ispatch, int which)
148 /* extract a branch of a diff or diff3 or merge output
151 struct stream f, flist[3];
155 "%s: no file given for --extract\n", Cmd);
160 "%s: only give one file for --extract\n", Cmd);
163 f = load_file(argv[0]);
164 if (f.body == NULL) {
166 "%s: cannot load file '%s' - %s\n", Cmd,
167 argv[0], strerror(errno));
171 if (split_patch(f, &flist[0], &flist[1]) == 0) {
173 "%s: No chunk found in patch: %s\n", Cmd,
178 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
180 "%s: merge file %s looks bad.\n", Cmd,
185 if (flist[which-'1'].body == NULL) {
187 "%s: %s has no -%c component.\n", Cmd,
191 if (write(1, flist[which-'1'].body,
192 flist[which-'1'].len)
193 != flist[which-'1'].len)
199 static int do_diff_lines(struct file fl[2], struct csl *csl)
204 while (a < fl[0].elcnt || b < fl[1].elcnt) {
206 if (fl[0].list[a].start[0]) {
213 } else if (b < csl->b) {
214 if (fl[1].list[b].start[0]) {
222 if (fl[0].list[a].start[0] == '\0')
223 printsep(fl[0].list[a],
232 if (a >= csl->a+csl->len)
239 static int do_diff_words(struct file fl[2], struct csl *csl)
243 int sol = 1; /* start of line */
245 while (a < fl[0].elcnt || b < fl[1].elcnt) {
255 for (a1 = a; a1 < csl->a ; a1++)
256 if (ends_line(fl[0].list[a1])) {
262 for (; a < csl->a ; a++) {
263 printword(stdout, fl[0].list[a]);
264 if (ends_line(fl[0].list[a])) {
277 printword(stdout, fl[0].list[a]);
278 sol = ends_line(fl[0].list[a]);
280 } while (a < csl->a);
281 printf("%s-->>>", sol ? "|" : "");
284 } else if (b < csl->b) {
289 for (b1 = b; b1 < csl->b; b1++)
290 if (ends_line(fl[1].list[b1])) {
296 for (; b < csl->b ; b++) {
297 printword(stdout, fl[1].list[b]);
298 if (ends_line(fl[1].list[b])) {
311 printword(stdout, fl[1].list[b]);
312 sol = ends_line(fl[1].list[b]);
314 } while (b < csl->b);
315 printf("%s++>>>", sol ? "|" : "");
322 for (a1 = a; a1 < csl->a+csl->len; a1++)
323 if (ends_line(fl[0].list[a1]))
326 if (fl[0].list[a].start[0]) {
328 for (; a < csl->a+csl->len; a++, b++) {
329 printword(stdout, fl[0].list[a]);
330 if (ends_line(fl[0].list[a])) {
336 printsep(fl[0].list[a], fl[1].list[b]);
343 printword(stdout, fl[0].list[a]);
344 if (ends_line(fl[0].list[a]))
349 if (a >= csl->a+csl->len)
356 static int do_diff(int argc, char *argv[], int obj, int ispatch,
357 int which, int reverse)
359 /* create a diff (line or char) of two streams */
360 struct stream f, flist[3];
361 int chunks1 = 0, chunks2 = 0, chunks3 = 0;
368 fprintf(stderr, "%s: no file given for --diff\n", Cmd);
371 f = load_file(argv[0]);
372 if (f.body == NULL) {
374 "%s: cannot load file '%s' - %s\n", Cmd,
375 argv[0], strerror(errno));
379 split_patch(f, &flist[0], &flist[1]);
380 if (!flist[0].body || !flist[1].body) {
382 "%s: couldn't parse patch %s\n", Cmd,
388 flist[0] = load_file(argv[0]);
389 if (flist[0].body == NULL) {
391 "%s: cannot load file '%s' - %s\n", Cmd,
392 argv[0], strerror(errno));
396 f = load_file(argv[1]);
397 if (f.body == NULL) {
399 "%s: cannot load patch '%s' - %s\n", Cmd,
400 argv[1], strerror(errno));
405 split_patch(f, &flist[2],
409 split_patch(f, &flist[1],
413 flist[1] = load_file(argv[1]);
414 if (flist[1].body == NULL) {
416 "%s: cannot load file '%s' - %s\n", Cmd,
417 argv[1], strerror(errno));
423 "%s: too many files given for --diff\n", Cmd);
431 fl[0] = split_stream(flist[0], obj);
432 fl[1] = split_stream(flist[1], obj);
433 if (!(obj & WholeWord) && fl[0].elcnt > 50000 && fl[1].elcnt > 50000) {
434 /* Too big - use fewer words if possible */
438 fl[0] = split_stream(flist[0], obj);
439 fl[1] = split_stream(flist[1], obj);
441 if (chunks2 && !chunks1)
442 csl = pdiff(fl[0], fl[1], chunks2);
444 csl = diff_patch(fl[0], fl[1]);
445 if ((obj & ByMask) == ByLine) {
447 printf("@@ -1,%d +1,%d @@\n",
448 fl[0].elcnt, fl[1].elcnt);
449 exit_status = do_diff_lines(fl, csl);
452 /* count lines in each file */
455 for (i = 0 ; i < fl[0].elcnt ; i++)
456 if (ends_line(fl[0].list[i]))
458 for (i = 0 ; i < fl[1].elcnt ; i++)
459 if (ends_line(fl[1].list[i]))
461 printf("@@ -1,%d +1,%d @@\n", l1, l2);
463 exit_status = do_diff_words(fl, csl);
468 static int do_merge(int argc, char *argv[], int obj, int blanks,
469 int reverse, int replace, char *outfilename,
470 int ignore, int show_wiggles,
473 /* merge three files, A B C, so changed between B and C get made to A
475 struct stream f, flist[3];
478 int chunks1 = 0, chunks2 = 0, chunks3 = 0;
479 char *replacename = NULL, *orignew = NULL;
480 struct csl *csl1, *csl2;
482 FILE *outfile = stdout;
486 fprintf(stderr, "%s: no files given for --merge\n", Cmd);
491 for (i = 0; i < argc; i++) {
492 flist[i] = load_file(argv[i]);
493 if (flist[i].body == NULL) {
494 fprintf(stderr, "%s: cannot load file '%s' - %s\n",
496 argv[i], strerror(errno));
502 fprintf(stderr, "%s: too many files given for --merge\n",
507 case 1: /* a merge file */
509 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
510 fprintf(stderr, "%s: merge file %s looks bad.\n",
516 case 2: /* a file and a patch */
518 chunks2 = chunks3 = split_patch(f, &flist[1], &flist[2]);
520 case 3: /* three separate files */
529 for (i = 0; i < 3; i++) {
530 if (flist[i].body == NULL) {
531 fprintf(stderr, "%s: file %d missing\n", Cmd, i);
536 outfile = fopen(outfilename, "w");
538 fprintf(stderr, "%s: could not create %s\n",
542 } else if (replace) {
544 replacename = xmalloc(strlen(argv[0]) + 20);
545 orignew = xmalloc(strlen(argv[0]) + 20);
546 strcpy(replacename, argv[0]);
547 strcpy(orignew, argv[0]);
548 strcat(orignew, ".porig");
549 if (open(orignew, O_RDONLY) >= 0 ||
551 fprintf(stderr, "%s: %s already exists\n",
558 strcat(replacename, "XXXXXX");
559 fd = mkstemp(replacename);
562 "%s: could not create temporary file for %s\n",
569 outfile = fdopen(fd, "w");
576 fl[0] = split_stream(flist[0], blanks);
577 fl[1] = split_stream(flist[1], blanks);
578 fl[2] = split_stream(flist[2], blanks);
579 if (!(blanks & WholeWord) &&
580 fl[1].elcnt > 50000 &&
581 (fl[0].elcnt > 50000 || fl[2].elcnt > 50000)) {
587 fl[0] = split_stream(flist[0], blanks);
588 fl[1] = split_stream(flist[1], blanks);
589 fl[2] = split_stream(flist[2], blanks);
592 if (chunks2 && !chunks1)
593 csl1 = pdiff(fl[0], fl[1], chunks2);
595 csl1 = diff(fl[0], fl[1]);
596 csl2 = diff_patch(fl[1], fl[2]);
598 ci = make_merger(fl[0], fl[1], fl[2], csl1, csl2,
599 obj == 'w', ignore, show_wiggles > 1);
600 print_merge(outfile, &fl[0], &fl[1], &fl[2],
601 obj == 'w', ci.merger, NULL, 0, 0);
602 if (!quiet && ci.conflicts)
604 "%d unresolved conflict%s found\n",
606 ci.conflicts == 1 ? "" : "s");
607 if (!quiet && ci.ignored)
609 "%d already-applied change%s ignored\n",
611 ci.ignored == 1 ? "" : "s");
618 if (stat(argv[0], &statbuf) != 0) {
620 "%s: failed to stat original file. - %s\n",
621 Cmd, strerror(errno));
626 if (fchmod(fileno(outfile), statbuf.st_mode) != 0) {
628 "%s: failed to change permission of new file. - %s\n",
629 Cmd, strerror(errno));
635 if (rename(argv[0], orignew) == 0 &&
636 rename(replacename, argv[0]) == 0)
640 "%s: failed to move new file into place.\n",
650 return ci.conflicts + ci.wiggles > 0;
652 return ci.conflicts > 0;
655 static int multi_merge(int argc, char *argv[], int obj, int blanks,
656 int reverse, int ignore, int show_wiggles,
657 int replace, int strip,
669 "%s: -p in merge mode requires -r\n",
675 "%s: -p in merge mode requires exactly one file\n",
680 f = fopen(filename, "r");
682 fprintf(stderr, "%s: cannot open %s\n",
686 check_dir(filename, fileno(f));
687 pl = parse_patch(f, NULL, &num_patches);
689 if (set_prefix(pl, num_patches, strip) == 0) {
690 fprintf(stderr, "%s: aborting\n", Cmd);
693 for (i = 0; i < num_patches; i++) {
696 asprintf(&name, "_wiggle_:%d:%d:%s",
697 pl[i].start, pl[i].end, filename);
700 rv |= do_merge(2, av, obj, blanks, reverse, 1, NULL, ignore,
701 show_wiggles, quiet);
706 int main(int argc, char *argv[])
717 int verbose = 0, quiet = 0;
721 int show_wiggles = 0;
724 char *outfile = NULL;
726 int ignore_blanks = 0;
728 trace = getenv("WIGGLE_TRACE");
732 while ((opt = getopt_long(argc, argv,
733 short_options, long_options,
734 &option_index)) != -1)
740 helpmsg = HelpExtract;
749 helpmsg = HelpBrowse;
752 fputs(helpmsg, stderr);
756 fputs(Version, stderr);
761 fputs(Usage, stderr);
772 if (mode == 'B' && opt == 'd') {
773 /* Browse/diff mode */
778 "%s: mode is '%c' - cannot set to '%c'\n",
783 ignore_blanks |= WholeWord;
788 if (obj == 0 || obj == opt) {
793 "%s: cannot select both words and lines.\n", Cmd);
811 ignore_blanks |= IgnoreBlanks;
828 if (which == 0 || which == opt) {
833 "%s: can only select one of -1, -2, -3\n", Cmd);
836 case 'p': /* 'patch' or 'strip' */
838 strip = atol(optarg);
857 vpatch(argc-optind, argv+optind, ispatch,
858 strip, reverse, replace, outfile, selftest,
859 ignore_blanks, backup);
860 /* should not return */
864 if (obj && mode == 'x') {
866 "%s: cannot specify --line or --word with --extract\n",
870 if (mode != 'm' && !obj)
872 if (ispatch && outfile) {
873 fprintf(stderr, "%s: --output incompatible with --patch\n",
877 if (replace && mode != 'm') {
879 "%s: --replace or --output only allowed with --merge\n", Cmd);
882 if (mode == 'x' && !which) {
884 "%s: must specify -1, -2 or -3 with --extract\n", Cmd);
887 if (mode != 'x' && mode != 'd' && which) {
889 "%s: -1, -2 or -3 only allowed with --extract or --diff\n",
894 if (ispatch && which == '3') {
896 "%s: cannot extract -3 from a patch.\n", Cmd);
902 exit_status = extract(argc-optind, argv+optind, ispatch, which);
905 exit_status = do_diff(argc-optind, argv+optind,
906 (obj == 'l' ? ByLine : ByWord)
908 ispatch, which, reverse);
912 exit_status = multi_merge(argc-optind,
920 exit_status = do_merge(
921 argc-optind, argv+optind,
922 obj, ignore_blanks, reverse, replace,
924 ignore, show_wiggles, quiet);